import qs from 'qs';
import { defaultConfig, application } from './config';
import { erroToText, toLabel, getFormData } from './tool';
import { IConfig, IRequestConfig, TRequest } from './types';

/**
 * 初始化请求模型
 * 返回链接参数以及各请求器
 */
const createRequest = (config: IRequestConfig) => {
  const {
    host,
    apiPath = '',
    photoPath = '',
    timeout = 5000,
    defaultConfig: defaultConfig_,
    interceptorsRequest = (config_: IConfig) => config_,
    interceptorsResponse = (_config: IConfig, res: any) => res,
  } = config;

  const baseURL = host + apiPath;
  const photo = host + photoPath;

  // 合并默认配置
  const defaultConfig__ = {
    ...defaultConfig,
    ...defaultConfig_,
  };

  /**
   * 执行请求
   * @param  {string}  url    请求地址
   * @param  {any}     config 请求配置
   * @return {Promise}        返回处理后的 Promise
   */
  const request = (url: string, config?: any) => {
    // 拼接地址
    url = /^http/.test(url) ? url : baseURL + url;

    // 合并配置并执行请求拦截
    config = interceptorsRequest({
      ...defaultConfig__,
      ...config,
    });

    // method 转大写
    config.method = config.method.toUpperCase();

    // 发出请求
    return Promise.race([
      fetch(url, config), // 追加请求超时
      new Promise((_, reject) => {
        setTimeout(() => reject('request timeout'), config.timeout || timeout);
      }),
    ])
      .then(response => response[config.dataFormat]()) // 转化响应数据
      .catch(
        error =>
          Promise.resolve({
            error,
            errorText: erroToText(error),
          }) // 错误类型检查
      )
      .then(res => interceptorsResponse(config, res)); // 载入响应拦截
  };

  const get: TRequest = (address, data, config) => {
    const data_ = qs.stringify(data);
    return request(`${address}${data_ && `?${data_}`}`, {
      data,
      address,
      ...toLabel(config),
    });
  };

  const post: TRequest = (address, data, config) => {
    config = toLabel(config);
    let body = ''; // 根据传递参数格式转换数据
    switch (config.headers ? config.headers['Content-Type'] : application.json) {
      case application.json:
        body = JSON.stringify(data);
        break;
      case application.form:
        body = qs.stringify(data);
        break;
    }
    if (body && ['{}', '[]'].indexOf(body) === -1) {
      config.body = body; // 不为空时把数据写入请求主体
    }
    return request(address, {
      method: 'post',
      data,
      address,
      ...config,
    });
  };

  const put: TRequest = (address, data, config) => {
    return post(address, data, {
      method: 'put',
      ...toLabel(config),
    });
  };

  const patch: TRequest = (address, data, config) => {
    return post(address, data, {
      method: 'patch',
      ...toLabel(config),
    });
  };

  const del: TRequest = (address, data, config) => {
    return post(address, data, {
      method: 'delete',
      ...toLabel(config),
    });
  };

  const upload: TRequest = (address, data, config) => {
    return request(address, {
      method: 'post',
      headers: {},
      address,
      data,
      body: getFormData(data),
      ...toLabel(config),
    });
  };

  return {
    baseURL,
    photo,
    get,
    post,
    put,
    patch,
    del,
    upload,
  };
};

export default createRequest;
